package com.github.cyf.upload.local;

import com.github.cyf.upload.local.pojo.LocalFileUploadResult;
import com.github.cyf.upload.local.pojo.UploadFileParam;
import com.github.cyf.upload.local.pojo.UploadProgress;
import com.github.cyf.upload.enums.SliceUploadStatus;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

/**
 * @author chenyifan
 * @create 2025-05-22 17:09
 */
public class LocalUpload {


    /**
     * 文件分片、断点续传上传程序
     * 创建 文件名.conf 文件记录已上传分片信息
     * 使用 RandomAccessFile(随机访问文件) 类随机指定位置写入文件,类似于合成分片
     * 检验分片文件是否全部上传完成，重命名缓存文件
     * @param param        上传文件时 需要接收的基本参数信息
     * @param filePath     文件保存的路径
     * @param chunkSize    每块分片的大小
     * @param request      HTTP Servlet请求
     * @return 返回文件上传状态
     */
    public static LocalFileUploadResult sliceFileUploader(UploadFileParam param, String filePath, long chunkSize, HttpServletRequest request) {
        if (StringUtils.isBlank(filePath) || param.getFile() == null) {
            throw new IllegalArgumentException("参数值为空");
        }
        // 判断 enctype 属性是否为multipart/form-data
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            throw new IllegalArgumentException("上传内容不是有效的multipart/form-data类型.");
        }

        File file = new File(filePath);
        File fileDir = file.getParentFile();
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        String fileName = file.getName();

        // _tmp的缓存文件对象
        File tmpFile = new File(fileDir, fileName + "_tmp");
        try(RandomAccessFile accessTmpFile = new RandomAccessFile(tmpFile, "rw")) {
            // 随机位置写入文件
            long offset = chunkSize * param.getChunk();
            // 定位到该分片的偏移量、写入该分片数据
            accessTmpFile.seek(offset);
            accessTmpFile.write(param.getFile().getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 分片配置文件
        File confFile = new File(fileDir, fileName + ".conf");
        try(RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw")) {
            // 把该分段标记为 true 表示完成
            accessConfFile.setLength(param.getChunks());
            accessConfFile.seek(param.getChunk());
            accessConfFile.write(Byte.MAX_VALUE);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        LocalFileUploadResult uploadResult = new LocalFileUploadResult();
        uploadResult.setChunk(param.getChunk());
        uploadResult.setSliceUploadComplete(true);

        // 检查是否全部分片都成功上传
        byte[] completeList;
        try {
            completeList = FileUtils.readFileToByteArray(confFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        byte isComplete = Byte.MAX_VALUE;
        for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
            // 与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
            isComplete = (byte) (isComplete & completeList[i]);
        }

        if (isComplete != Byte.MAX_VALUE) {
            uploadResult.setUploadComplete(false);
            return uploadResult;
        }

        // 所有分片上传完成，重命名文件
        boolean isSuccess = tmpFile.renameTo(file);
        if (!isSuccess) {
            throw new RuntimeException("文件重命名时失败");
        }
        // 删除配置文件
        confFile.delete();
        uploadResult.setUploadComplete(true);
        uploadResult.setFilePath(file.getAbsolutePath());
        return uploadResult;
    }

    /**
     * 获取分片上传进度
     * @param filePath 文件路径
     */
    public static UploadProgress getUploadProgress(String filePath) {
        File file = new File(filePath);
        File fileDir = file.getParentFile();
        String fileName = file.getName();
        // _tmp的缓存文件对象
        File tmpFile = new File(fileDir, fileName + "_tmp");
        File confFile = new File(fileDir, fileName + ".conf");
        // 分片记录文件 和 文件缓存文件 同时存在,则表示文件上传了一部分
        if (confFile.exists() && tmpFile.exists()) {
            byte[] completeList;
            try {
                completeList = FileUtils.readFileToByteArray(confFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            List<Integer> missChunkList = new ArrayList<>();
            for (int i = 0; i < completeList.length; i++) {
                if (completeList[i] != Byte.MAX_VALUE) {
                    missChunkList.add(i);
                }
            }
            return UploadProgress.builder()
                    .uploadStatus(SliceUploadStatus.FILE_UPLOAD_SOME.getValue())
                    .missChunks(missChunkList)
                    .build();
        }

        // 上传的文件对象存在, 则表示文件上传成功
        if (file.exists()) {
            return UploadProgress.builder().uploadStatus(SliceUploadStatus.FILE_UPLOADED.getValue()).build();
        }
        // 文件都不存在,则表示文件还没有上传
        return UploadProgress.builder().uploadStatus(SliceUploadStatus.FILE_NO_UPLOAD.getValue()).build();
    }

}